﻿//QuikDdeFormatter.cs
//Copyright (c) 2013 StockSharp LLC, all rights reserved.
//This code module is part of StockSharp library.
//This code is licensed under the GNU GENERAL PUBLIC LICENSE Version 3.
//See the file License.txt for the license details.
//More info on: http://stocksharp.com

namespace StockSharp.Quik
{
	using System;
	using System.Collections.Generic;
	using System.Linq;

	using Ecng.Collections;
	using Ecng.Common;

	using StockSharp.Algo;
	using StockSharp.Algo.Storages;
	using StockSharp.BusinessEntities;

	enum DerivativeLimitTypes
	{
		Money,
		Bail,
		Clearing,
		ClearingBail,
		Spot,
		Term,
	}

	static class QuikDdeFormatter
	{
		public static void Deserialize(this DdeTable table, IList<IList<object>> rows, Action<IList<object>, Func<DdeTableColumn, object>> handler, Action<Exception> errorHandler, bool skipErrors)
		{
			if (table == null)
				throw new ArgumentNullException("table");

			if (rows == null)
				throw new ArgumentNullException("rows");

			if (handler == null)
				throw new ArgumentNullException("handler");

			if (errorHandler == null)
				throw new ArgumentNullException("errorHandler");

			try
			{
				var errors = new List<Exception>();

				foreach (var r in rows)
				{
					try
					{
						var row = r;

						handler(row, column =>
						{
							var index = table.Columns.IndexOf(column);

							if (index == -1)
								throw new InvalidOperationException("Для таблицы '{0}' не была найдена колонка '{1}'.".Put(table.Caption, column.Name));

							if (row.Count <= index)
								throw new InvalidOperationException("Данные для таблицы '{0}' экспортировались в меньшем формате. Не было найдено значение для колонки '{1}'. Количество колонок равно '{2}', индекс колонки '{3}'.".Put(table.Caption, column.Name, row.Count, index));

							return row[index];
						});
					}
					catch (Exception ex)
					{
						if (skipErrors)
							errors.Add(ex);
						else
							throw;
					}
				}

				foreach (var error in errors)
					errorHandler(error);
			}
			catch (Exception ex)
			{
				errorHandler(ex);
			}
		}

		public static OrderStates? GetState(this Func<DdeTableColumn, object> func, DdeTableColumn column)
		{
			if (func == null)
				throw new ArgumentNullException("func");

			var value = (string)func(column);

			switch (value)
			{
				case "ACTIVE":
					return OrderStates.Active;
				case "FILLED":
					return OrderStates.Done;
				case "KILLED":
					return null;
				default:
					throw new ArgumentOutOfRangeException("column", value, "Неправильное значение для состояния заявки.");
			}
		}

		public static OrderDirections ToDirection(this object value)
		{
			if (value == null)
				throw new ArgumentNullException("value");

			switch ((string)value)
			{
				case "B":
					return OrderDirections.Buy;
				case "S":
					return OrderDirections.Sell;
				default:
					throw new ArgumentOutOfRangeException("value", value, "Неправильное значение для направления заявки.");
			}
		}

		public static DateTime? GetExpiryDate(this Func<DdeTableColumn, object> func, DdeTableColumn column)
		{
			if (func == null)
				throw new ArgumentNullException("func");

			var expiryDate = func.Get<string>(column);
			return expiryDate.CompareIgnoreCase("до отмены") || expiryDate.IsEmpty() ? DateTime.MaxValue : expiryDate.To<DateTime>();
		}

		public static T Get<T>(this Func<DdeTableColumn, object> func, DdeTableColumn column)
		{
			if (func == null)
				throw new ArgumentNullException("func");

			var value = func(column);
			return Get<T>(value, column);
		}

		private static T Get<T>(object value, DdeTableColumn column)
		{
			if (column == null)
				throw new ArgumentNullException("column");

			try
			{
				return value.To<T>();
			}
			catch (Exception ex)
			{
				throw new ArgumentException("Невозможно для колонки {0} привести значение '{1}' к типу {2}.".Put(column.Name, value, typeof(T).Name), "value", ex);
			}
		}

		public static object Get(object value, DdeTableColumn column)
		{
			if (column == null)
				throw new ArgumentNullException("column");

			try
			{
				if (value is string && (string)value == string.Empty)
					return null;
				else
					return value.To(column.DataType);
			}
			catch (Exception ex)
			{
				throw new ArgumentException("Невозможно для колонки {0} привести значение '{1}' к типу {2}.".Put(column.Name, value, column.DataType.Name), "value", ex);
			}
		}

		public static T GetNullable<T>(this Func<DdeTableColumn, object> func, DdeTableColumn column, T defaultValue = default(T))
			where T : struct
		{
			return func.GetNullable2<T>(column) ?? defaultValue;
		}

		public static T? GetNullable2<T>(this Func<DdeTableColumn, object> func, DdeTableColumn column)
			where T : struct
		{
			if (func == null)
				throw new ArgumentNullException("func");

			var value = func(column);

			if (value is T)
				return (T)value;
			else
			{
				if (value is string && (string)value == string.Empty)
					return null;
				else
					return Get<T>(value, column);
			}
		}

		public static T? GetZeroable<T>(this Func<DdeTableColumn, object> func, DdeTableColumn column)
			where T : struct
		{
			var value = func.Get<double>(column);
			return value == 0 ? (T?)null : Get<T>(value, column);
		}

		public static Unit GetUnit(this Func<DdeTableColumn, object> func, DdeTableColumn typeColumn, DdeTableColumn valueColumn)
		{
			UnitTypes? type;

			var typeStr = Get<string>(func, typeColumn);

			switch (typeStr)
			{
				case "":
					type = null;
					break;
				case "Д":
					type = UnitTypes.Absolute;
					break;
				case "%":
					type = UnitTypes.Percent;
					break;
				default:
					throw new ArgumentOutOfRangeException("func", typeStr, "Неправильное значение для колонки {0}.".Put(typeColumn.Name));
			}

			return type == null ? null : new Unit { Value = Get<decimal>(func, valueColumn), Type = (UnitTypes)type };
		}

		public static bool? GetBool(this Func<DdeTableColumn, object> func, DdeTableColumn column)
		{
			var value = Get<string>(func, column);

			switch (value)
			{
				case "":
					return null;
				case "Да":
					return true;
				case "Нет":
					return false;
				default:
					throw new ArgumentOutOfRangeException("func", value, "Неправильно значение для колонки {0}.".Put(column.Name));
			}
		}

		public static QuikOrderConditionTypes GetStopOrderType(this Func<DdeTableColumn, object> func)
		{
			var value = Get<string>(func, DdeStopOrderColumns.Type);

			switch (value)
			{
				case "Со связ. заявкой":
					return QuikOrderConditionTypes.LinkedOrder;
				case "Стоп-лимит":
				case "Стоп-лимит по заявке":
					return QuikOrderConditionTypes.StopLimit;
				case "СЦ по др. бумаге":
					return QuikOrderConditionTypes.OtherSecurity;
				case "Тэйк-профит":
				case "Тэйк профит по заявке":
					return QuikOrderConditionTypes.TakeProfit;
				case "Тэйк-профит и стоп-лимит":
				case "Тэйк-профит и стоп-лимит по заявке":
					return QuikOrderConditionTypes.TakeProfitStopLimit;
				default:
					throw new ArgumentOutOfRangeException("func", value, "Неподдерживаемый тип стоп-заявки.");
			}
		}

		public static OrderTypes GetOrderType(this Func<DdeTableColumn, object> func)
		{
			var type = func.Get<string>(DdeOrderColumns.Type);
			return type[0] == 'L' ? OrderTypes.Limit : OrderTypes.Market;
		}

		public static OrderExecutionConditions GetExecutionCondition(this Func<DdeTableColumn, object> func)
		{
			var type = func.Get<string>(DdeOrderColumns.Type);

			switch (type[2])
			{
				case 'K':
					return OrderExecutionConditions.MatchOrCancel;
				case 'Q':
					return OrderExecutionConditions.PutInQueue;
				default:
					return OrderExecutionConditions.CancelBalance;
			}
		}

		public static SecurityStates GetSecurityState(this Func<DdeTableColumn, object> func)
		{
			var state = func.Get<string>(DdeSecurityColumns.Status);
			return state == "торгуется" ? SecurityStates.Trading : SecurityStates.Stoped;
		}

		public static QuikStopPriceConditions GetStopPriceCondition(this Func<DdeTableColumn, object> func)
		{
			return func.Get<string>(DdeStopOrderColumns.StopPriceCondition) == ">=" ? QuikStopPriceConditions.MoreOrEqual : QuikStopPriceConditions.LessOrEqual;
		}

		public static QuikOrderConditionResults? GetStopResult(this Func<DdeTableColumn, object> func)
		{
			if (func == null)
				throw new ArgumentNullException("func");

			var value = func.Get<string>(DdeStopOrderColumns.Result);

			switch (value)
			{
				case "":
					return null;
				case "ORDER SENT TO TS":
					return QuikOrderConditionResults.SentToTS;
				case "REJECTED BY TS":
					return QuikOrderConditionResults.RejectedByTS;
				case "KILLED":
					return QuikOrderConditionResults.Killed;
				case "COORDER KILLED":
					return QuikOrderConditionResults.LinkedOrderKilled;
				case "COORDER FILLED":
					return QuikOrderConditionResults.LinkedOrderFilled;
				case "WAITING FOR ACTIVATION":
					return QuikOrderConditionResults.WaitingForActivation;
				case "CALCULATE MIN/MAX":
					return QuikOrderConditionResults.CalculateMinMax;
				case "LIMITS CHECK FAILED":
					return QuikOrderConditionResults.LimitControlFailed;
				case "WAITING FOR ACTIVATION AND CALCULATE MIN/MAX":
					return QuikOrderConditionResults.CalculateMinMaxAndWaitForActivation;
				default:
					throw new ArgumentOutOfRangeException("func", value, "Неправильное значение для результата исполнения стоп-заявки.");
			}
		}

		public static DerivativeLimitTypes GetLimitType(this Func<DdeTableColumn, object> func)
		{
			if (func == null)
				throw new ArgumentNullException("func");

			var value = (string)func(DdeDerivativePortfolioColumns.LimitType);

			switch (value)
			{
				case "Ден.средства":
				case "Рубли":
					return DerivativeLimitTypes.Money;
				case "Залоговые ден.средства":
				case "Залоговые рубли":
					return DerivativeLimitTypes.Bail;
				case "Клиринговые ден.средства":
				case "Клиринговые рубли":
					return DerivativeLimitTypes.Clearing;
				case "Клиринговые залоговые ден.средства":
				case "Клиринговые залоговые рубли":
					return DerivativeLimitTypes.ClearingBail;
				case "Лимит открытых позиций на спот-рынке":
					return DerivativeLimitTypes.Spot;
				case "По совокупным средствам":
					return DerivativeLimitTypes.Term;
				default:
					throw new ArgumentOutOfRangeException("func", value, "Неправильное значение для типа лимита.");
			}
		}

		public static OptionTypes? GetOptionType(this Func<DdeTableColumn, object> func)
		{
			switch (func.Get<string>(DdeSecurityColumns.OptionType))
			{
				case "Call":
					return OptionTypes.Call;
				case "Put":
					return OptionTypes.Put;
				default:
					return null;
			}
		}

		public static string ToCurrencyName(this CurrencyTypes type)
		{
			switch (type)
			{
				case CurrencyTypes.RUB:
					return "SUR";
				default:
					return type.GetName();
			}
		}

		public static CurrencyTypes ToCurrencyType(this string name)
		{
			switch (name)
			{
				case "SUR":
					return CurrencyTypes.RUB;
				default:
					return name.To<CurrencyTypes>();
			}
		}

		public static Quote BuildBid(this IEnumerable<DdeTableColumn> columns, Security security, Func<DdeTableColumn, object> func)
		{
			return columns.BuildQuote(security, OrderDirections.Buy, DdeSecurityColumns.BestBidPrice, DdeSecurityColumns.BestBidVolume, func);
		}

		public static Quote BuildAsk(this IEnumerable<DdeTableColumn> columns, Security security, Func<DdeTableColumn, object> func)
		{
			return columns.BuildQuote(security, OrderDirections.Sell, DdeSecurityColumns.BestAskPrice, DdeSecurityColumns.BestAskVolume, func);
		}

		private static Quote BuildQuote(this IEnumerable<DdeTableColumn> columns, Security security, OrderDirections direction, DdeTableColumn priceColumn, DdeTableColumn volumeColumn, Func<DdeTableColumn, object> func)
		{
			var containsBestBidPrice = columns.Contains(priceColumn);
			var containsBestBidVolume = columns.Contains(volumeColumn);

			if (containsBestBidPrice || containsBestBidVolume)
			{
				var price = containsBestBidPrice ? func.GetNullable<decimal>(priceColumn) : 0;
				var volume = containsBestBidVolume ? func.GetNullable<decimal>(volumeColumn) : 0;

				if (price != 0 || volume != 0)
				{
					return new Quote
					{
						Security = security,
						OrderDirection = OrderDirections.Buy,
						Price = price,
						Volume = volume,
					};
				}
			}

			return null;
		}

		public static Trade BuildTrade(this IEnumerable<DdeTableColumn> columns, Func<Trade> createTrade, Func<DdeTableColumn, object> func)
		{
			DateTime? lastTradeTime = null;

			if (columns.Contains(DdeSecurityColumns.LastTradeTime))
				lastTradeTime = func.GetNullable2<DateTime>(DdeSecurityColumns.LastTradeTime);
			else if (columns.Contains(DdeSecurityColumns.LastChangeTime))
				lastTradeTime = func.GetNullable2<DateTime>(DdeSecurityColumns.LastChangeTime);

			decimal? lastTradeVolume = null;

			if (columns.Contains(DdeSecurityColumns.LastTradeVolume))
				lastTradeVolume = func.GetNullable2<decimal>(DdeSecurityColumns.LastTradeVolume);
			else if (columns.Contains(DdeSecurityColumns.LastTradeVolume2))
				lastTradeVolume = func.GetNullable2<decimal>(DdeSecurityColumns.LastTradeVolume2);

			decimal? lastTradePrice = null;

			if (columns.Contains(DdeSecurityColumns.LastTradePrice))
				lastTradePrice = func.GetNullable<decimal>(DdeSecurityColumns.LastTradePrice);

			if (
					(lastTradeTime != null && lastTradeTime != default(DateTime)) ||
					(lastTradeVolume != null && lastTradeVolume != default(decimal)) ||
					(lastTradePrice != null && lastTradePrice != default(decimal))
				)
			{
				var lastTrade = createTrade();

				if (lastTradeTime != null)
				{
					lastTrade.Time = (DateTime)lastTradeTime;
					lastTrade.InitLatency();
				}

				if (lastTradeVolume != null)
					lastTrade.Volume = (decimal)lastTradeVolume;

				if (lastTradePrice != null)
					lastTrade.Price = (decimal)lastTradePrice;

				return lastTrade;
			}
			else
				return null;
		}

		private static SynchronizedDictionary<DdeTableColumn, Func<Security, DateTime, object, SecurityChange>> _ddeColumnValueToSecurityChangeConverters;

		public static SynchronizedDictionary<DdeTableColumn, Func<Security, DateTime, object, SecurityChange>> DdeColumnValueToSecurityChangeConverters
		{
			get { return _ddeColumnValueToSecurityChangeConverters ?? (_ddeColumnValueToSecurityChangeConverters = CreateDdeColumnValueToSecurityChangeConverters()); }
		}

		private static SynchronizedDictionary<DdeTableColumn, Func<Security, DateTime, object, SecurityChange>> CreateDdeColumnValueToSecurityChangeConverters()
		{
			return new SynchronizedDictionary<DdeTableColumn, Func<Security, DateTime, object, SecurityChange>>
			{
				{ DdeSecurityColumns.OpenPrice, (s, t, v) => new SecurityChange(s, t, SecurityChangeTypes.OpenPrice, v.To<decimal>()) },
				{ DdeSecurityColumns.HighPrice, (s, t, v) => new SecurityChange(s, t, SecurityChangeTypes.HighPrice, v.To<decimal>()) },
				{ DdeSecurityColumns.LowPrice, (s, t, v) => new SecurityChange(s, t, SecurityChangeTypes.LowPrice, v.To<decimal>()) },
				{ DdeSecurityColumns.ClosePrice, (s, t, v) => new SecurityChange(s, t, SecurityChangeTypes.ClosePrice, v.To<decimal>()) },
				{ DdeSecurityColumns.MinStepPrice, (s, t, v) => new SecurityChange(s, t, SecurityChangeTypes.MinStepPrice, v.To<decimal>()) },
				{ DdeSecurityColumns.ImpliedVolatility, (s, t, v) => new SecurityChange(s, t, SecurityChangeTypes.ImpliedVolatility, v.To<decimal>()) },
				{ DdeSecurityColumns.TheorPrice, (s, t, v) => new SecurityChange(s, t, SecurityChangeTypes.TheorPrice, v.To<decimal>()) },
				{ DdeSecurityColumns.OpenPositions, (s, t, v) => new SecurityChange(s, t, SecurityChangeTypes.OpenInterest, v.To<decimal>()) },
				{ DdeSecurityColumns.MinPrice, (s, t, v) => new SecurityChange(s, t, SecurityChangeTypes.MinPrice, v.To<decimal>()) },
				{ DdeSecurityColumns.MaxPrice, (s, t, v) => new SecurityChange(s, t, SecurityChangeTypes.MaxPrice, v.To<decimal>()) },
				{ DdeSecurityColumns.BidsCount, (s, t, v) => new SecurityChange(s, t, SecurityChangeTypes.BidsCount, v.To<decimal>()) },
				{ DdeSecurityColumns.BidsVolume, (s, t, v) => new SecurityChange(s, t, SecurityChangeTypes.BidsVolume, v.To<decimal>()) },
				{ DdeSecurityColumns.AsksCount, (s, t, v) => new SecurityChange(s, t, SecurityChangeTypes.AsksCount, v.To<decimal>()) },
				{ DdeSecurityColumns.AsksVolume, (s, t, v) => new SecurityChange(s, t, SecurityChangeTypes.AsksVolume, v.To<decimal>()) },
				{ DdeSecurityColumns.MarginBuy, (s, t, v) => new SecurityChange(s, t, SecurityChangeTypes.MarginBuy, v.To<decimal>()) },
				{ DdeSecurityColumns.MarginSell, (s, t, v) => new SecurityChange(s, t, SecurityChangeTypes.MarginSell, v.To<decimal>()) },
			};
		}
	}
}